﻿using System;
using System.Collections.Generic;
using System.Text;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.MessageBus;
using NLog;
using NGinnBPM.Lib.Schema;
using NGinnBPM.Lib.Data;
using NGinnBPM.Lib.Exceptions;
using NGinnBPM.Services;
using NGinnBPM.Runtime.Events;
using NGinnBPM.Runtime.Messages;
using System.Transactions;
using NGinnBPM.Lib.Messages;
using NGinnBPM.Lib.Interfaces.Worklist;
using NGinnBPM.Lib.Operations.ExternalServices;
using NGinnBPM.Lib.Operations;

namespace NGinnBPM.Runtime
{
    /// <summary>
    /// NGinn environment implementation
    /// </summary>
    public class NGEnvironment : MarshalByRefObject, INGEnvironment, 
        IMessageConsumer<ProcessStartedEvent>,
        IMessageConsumer<ProcessCompletedEvent>,
        IMessageConsumer<StartProcessMessage>,
        IMessageConsumer<EnableTaskMessage>,
        IMessageConsumer<EnableMultiInstanceTaskMessage>,
        IMessageConsumer<CancelTaskMessage>,
        IMessageConsumer<FailTaskMessage>,
        IMessageConsumer<WorkItemCompleted>,
        IMessageConsumer<StartSubprocessMessage>,
        IMessageConsumer<ITaskInstanceMessage>
    {
        private IMessageBus _internalNGinnBus;
        private ITaskInstanceRepository _taskRepository;
        private IDefaultTaskInstanceFactory _taskFactory;
        private IProcessPackageRepository _packageStore;
        private Logger log = LogManager.GetCurrentClassLogger();
        private IResourceLockManager _processLockManager;
        private IProcessScriptManager _scriptManager;
        private IBarrierRegistry _barrierRegistry;
        private IMessageCorrelationIdResolver _correlationIdResolver;
        private IServiceResolver _srvLocator;

        public NGEnvironment()
        {
            log.Info("Created NGEnvironment {0}", this.GetHashCode());
        }

        public IBarrierRegistry BarrierRegistry
        {
            get { return _barrierRegistry; }
            set { _barrierRegistry = value; }
        }

        public IMessageBus InternalNGinnBus
        {
            get { return _internalNGinnBus; }
            set 
            { 
                _internalNGinnBus = value;
            }
        }

        public ITaskInstanceSerializer TaskInstanceSerializer { get; set; }

        /// <summary>
        /// Service locator that can be used by tasks 
        /// for accessing external services
        /// </summary>
        public IServiceResolver ServiceLocator
        {
            get { return _srvLocator; }
            set { _srvLocator = value; }
        }

        public IDefaultTaskInstanceFactory TaskInstanceFactory
        {
            get { return _taskFactory; }
            set { _taskFactory = value; }
        }

        public ITaskInstanceRepository TaskInstanceRepository
        {
            get { return _taskRepository; }
            set { _taskRepository = value; }
        }

        public ITaskDatabaseSessionFactory NGinnDbSessionFactory { get; set; }

        public IMessageCorrelationIdResolver MessageCorrelationIdResolver
        {
            get { return _correlationIdResolver; }
            set { _correlationIdResolver = value; }
        }
        /// <summary>
        /// Process package repository
        /// </summary>
        public IProcessPackageRepository PackageRepository
        {
            get { return _packageStore; }
            set 
            { 
                _packageStore = value;
                if (value is IProcessScriptManager && _scriptManager == null)
                    _scriptManager = (IProcessScriptManager)value;
            }
        }

        public IResourceLockManager ProcessInstanceLockManager
        {
            get { return _processLockManager; }
            set { _processLockManager = value; }
        }

        public IProcessScriptManager ScriptManager
        {
            get { return _scriptManager; }
            set { _scriptManager = value; }
        }

        private TransactionScope BeginTransaction()
        {
            TransactionOptions top = new TransactionOptions();
            top.Timeout = TimeSpan.FromSeconds(60.0);
            top.IsolationLevel = IsolationLevel.ReadCommitted;
            return new TransactionScope(TransactionScopeOption.Required, top);
        }

        /// <summary>
        /// If true, NGinn will not persist atomic tasks that have completed synchronously when enabling.
        /// If false all tasks will be persisted.
        /// TODO: RestartTask will not work without persisting everything.
        /// </summary>
        public bool DontPersistCompletedTasks { get; set; }

        /// <summary>
        /// Child task updates its parent synchronously without publishing a message
        /// to the bus. 
        /// TODO: implement
        /// </summary>
        public bool NotifyParentTaskSynchronously { get; set; }
        /// <summary>
        /// Use message bus for scheduling tasks
        /// </summary>
        public bool ScheduleTasksWithMessageBus { get; set; }
        
        #region INGEnvironment Members

        public string StartProcess(string definitionId, Dictionary<string, object> inputData, string startedBy, string correlationId)
        {
            string s = null;
            InTaskDbUpdateTransaction(delegate(ITaskDatabaseSession ses)
            {
                s = StartProcessInternal(definitionId, inputData, startedBy, correlationId, null);
            });
            return s;
            
        }

        public string StartSubprocess(string definitionId, Dictionary<string, object> inputData, string parentTaskInstance)
        {
            string ret = null;
            InTaskDbUpdateTransaction(delegate(ITaskDatabaseSession ses)
            {
                ret = StartProcessInternal(definitionId, inputData, null, null, parentTaskInstance);
            });
            return ret;
        }

        /// <summary>
        /// Starts a process instance
        /// </summary>
        /// <param name="definitionId"></param>
        /// <param name="inputData"></param>
        /// <param name="startedBy"></param>
        /// <param name="correlationId"></param>
        /// <param name="parentTaskInstance"></param>
        /// <returns></returns>
        protected string StartProcessInternal(string definitionId, Dictionary<string, object> inputData, string startedBy, string correlationId, string parentTaskInstance)
        {
            string id = NewInstanceId();
            try
            {
                NLog.MappedDiagnosticsContext.Set("ng_inst", id);

                ProcessDefinition pd = PackageRepository.GetProcess(definitionId);
                if (pd == null) throw new ProcessDefinitionException(definitionId, null, "Process definition not found: " + definitionId);
                DataUtil.Validate(inputData, pd.GetInputDataSchema());
                CurrentMessageInfo cmi = InternalNGinnBus.CurrentMessageInfo;
                ProcessInstance pi = new ProcessInstance();
                pi.InstanceId = id;
                pi.ProcessInstanceId = pi.InstanceId;
                pi.ProcessDefinitionId = pd.DefinitionId;
                pi.ProcessCorrelationId = correlationId;
                pi.StartedBy = startedBy;
                pi.TaskId = pd.Body.Id;
                pi.ParentTaskInstanceId = parentTaskInstance;
                if (cmi != null && (cmi.Body is StartSubprocessMessage || cmi.Body is StartProcessMessage))
                {   //get some info from the message
                    pi.NotifyEndpoint = cmi.Sender;
                    if (pi.ProcessCorrelationId == null) pi.ProcessCorrelationId = cmi.CorrelationId;
                }
                TaskContext tc = CreateTaskContext(definitionId, pd.Body.Id);
                pi.Activate(tc);
                pi.Enable(inputData);
                log.Debug("Continue: {0}", pi.CanContinue);
                pi.Passivate();
                TaskInstanceRepository.InsertTaskInstance(pi);

                InternalNGinnBus.NewMessage(new TaskKickableMessage(id))
                    .SetNonPersistentLocal(ScheduleTasksWithMessageBus ? false : true).Publish();
            }
            catch (Exception ex)
            {
                log.Error("Error starting process {0}: {1}", definitionId, ex);
                throw;
            }
            finally
            {
                NLog.MappedDiagnosticsContext.Remove("ng_inst");
            }
            return id;
        }
        
        public IList<string> GetKickableTasks()
        {
            return TaskInstanceRepository.GetTaskInstancesReadyForProcessing(50);
        }

        public void KickTask(string instanceId)
        {
            ModifyTask(instanceId, delegate(TaskInstance ti)
            {
                if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                if (!(ti is CompositeTaskInstance)) throw new Exception("Can't kick this task");
                CompositeTaskInstance cti = (CompositeTaskInstance)ti;
                cti.DoContinue();
            });
        }

        public void CancelProcessInstance(string instanceId)
        {
            CancelTaskInstance(instanceId);
        }

        public void CancelTaskInstance(string instanceId)
        {
            ModifyTask(instanceId, delegate(TaskInstance ti)
            {
                if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                ti.Cancel();
            });
        }

        /// <summary>
        /// TODO: implement
        /// </summary>
        /// <param name="messageCorrelationId"></param>
        /// <param name="messageBody"></param>
        public void DispatchProcessMessage(string messageCorrelationId, Dictionary<string, object> messageBody)
        {
            try
            {
                log.Info("Dispatching task message with correlation id: {0}", messageCorrelationId);
                string instanceId = MessageCorrelationIdResolver.GetReceiveTaskInstanceId(messageCorrelationId);
                if (instanceId == null)
                {
                    throw new NGinnException("Receive task not found for the message: " + messageCorrelationId).SetPermanent(false);
                }
                ModifyTask(instanceId, delegate(TaskInstance ti)
                {
                    ti.Complete("", messageBody);
                });
            }
            catch (Exception ex)
            {
                log.Error("Error handling message {0}: {1}", messageCorrelationId, ex);
                throw;
            }
        }

        private TaskContext CreateTaskContext(string definitionId, string taskId)
        {
            TaskContext tc = new TaskContext();
            tc.Environment = this;
            tc.ParentProcess = PackageRepository.GetProcess(definitionId);
            tc.MessageBus = this.InternalNGinnBus;
            
            return tc;
        }

        /// <summary>
        /// Generate new instance ID
        /// </summary>
        /// <returns></returns>
        public string NewInstanceId()
        {
            return GuidCombGenerator.Generate().ToString("N");
        }

        private MultiTaskInstance CreateMultiTask(ProcessDefinition pd, string taskId)
        {
            MultiTaskInstance mti = new MultiTaskInstance();
            mti.TaskId = taskId;
            mti.ProcessDefinitionId = pd.DefinitionId;
            mti.ParentProcess = pd;
            return mti;
        }
        /// <summary>
        /// Enable a task. Function is transactional - either a task is created and enabled, or an error occurs and nothing is done.
        /// </summary>
        /// <param name="definitionId">Process definition ID</param>
        /// <param name="taskId">Task ID</param>
        /// <param name="inputData">Task input data</param>
        /// <param name="parentTaskInstance">Parent task instance ID (optional)</param>
        /// <param name="parentProcessInstance">Parent process instance ID (optional)</param>
        /// <param name="sharedId">Shared id - for deferred choice tasks sharing same input tokens (optional)</param>
        /// <param name="instanceId">Task instance ID. If null, system will generate an instance ID. </param>
        /// <returns>Instance ID of newly created task</returns>
        private TaskInstance EnableTaskInternal(string definitionId, string taskId, Dictionary<string, object> inputData, string parentTaskInstance, string parentProcessInstance, string sharedId, string instanceId)
        {
            try
            {
                if (instanceId == null) instanceId = NewInstanceId();
                NLog.MappedDiagnosticsContext.Set("ng_inst", instanceId);
                
                ProcessDefinition pd = PackageRepository.GetProcess(definitionId);
                Task tsk = pd.RequireTask(taskId);
                DataUtil.Validate(inputData, tsk.GetInputDataSchema());
                TaskInstance parent = null;
                if (parentTaskInstance != null)
                {
                    parent = TaskInstanceRepository.GetTaskInstance(parentTaskInstance);
                    if (parent == null)
                        throw new TaskRuntimeException("Parent task not found: " + parentTaskInstance).SetProcessDef(definitionId).SetTaskId(taskId);
                }

                TaskInstance ti = TaskInstanceFactory.CreateTaskInstance(tsk);

                ti.InstanceId = instanceId;
                ti.TaskId = tsk.Id;
                ti.ProcessDefinitionId = definitionId;
                ti.ParentTaskInstanceId = parentTaskInstance;
                ti.ProcessInstanceId = parentProcessInstance;
                if (parent != null)
                {
                    ti.ProcessStartDate = parent.ProcessStartDate;
                }
                ti.Activate(CreateTaskContext(definitionId, taskId));
                ti.Enable(inputData);
                ti.Passivate();
                bool persist = true;
                if (ti.Status == TaskStatus.Completed || ti.Status == TaskStatus.Failed || ti.Status == TaskStatus.Cancelled)
                {
                    if (ti is AtomicTaskInstance && DontPersistCompletedTasks)
                        persist = false;
                }
                if (persist)
                {
                    TaskInstanceRepository.InsertTaskInstance(ti);
                    if (ti is CompositeTaskInstance || ti is ProcessInstance)
                    {
                        if (ti.CanContinue)
                        {
                            InternalNGinnBus.NewMessage(new TaskKickableMessage(ti.InstanceId))
                                .SetNonPersistentLocal(ScheduleTasksWithMessageBus ? false : true).Publish();
                        }
                    }
                }
                else
                {
                    log.Info("Task {0} completed on creation and will not be persisted.");
                }
                return ti;
            }
            catch (Exception ex)
            {
                log.Error("Error starting task insstance {0}/{1}: {2}", definitionId, taskId, ex);
                throw;
            }
            finally
            {
                NLog.MappedDiagnosticsContext.Remove("ng_inst");
            }
        }

        public string EnableTask(string definitionId, string taskId, Dictionary<string, object> inputData, string parentTaskInstance, string parentProcessInstance)
        {
            string ret = null;
            InTaskDbUpdateTransaction(delegate(ITaskDatabaseSession ses)
            {
                TaskInstance ti = EnableTaskInternal(definitionId, taskId, inputData, parentTaskInstance, parentProcessInstance, null, NewInstanceId());
                ret = ti.InstanceId;
            });
            return ret;
        }

        #endregion

        


        public string GetActiveInstanceOfTask(string taskId, string parentTaskInstanceId)
        {
            string ret = null;
            ReadTask(parentTaskInstanceId, delegate(TaskInstance ti)
            {
                if (ti == null) throw new TaskInstanceNotFoundException(parentTaskInstanceId);
                if (!(ti is CompositeTaskInstance))
                    throw new TaskRuntimeException("Not a composite task").SetInstanceId(parentTaskInstanceId);
                CompositeTaskInstance ct = (CompositeTaskInstance)ti;
                TransitionInfo tr = ct.GetActiveInstanceOfTask(taskId);
                if (tr != null)
                    ret = tr.InstanceId;
            });
            return ret;
        }

        private Dictionary<string, string> _currentlyModifiedTasks = new Dictionary<string, string>();

        private void InTaskDbUpdateTransaction(Action<ITaskDatabaseSession> act)
        {
            if (Transaction.Current == null)
            {
                using (TransactionScope ts = BeginTransaction())
                {
                    InTaskDbUpdateTransaction(act);
                    ts.Complete();
                }
                return;
            }

            if (TaskDatabaseSession.CurrentSession != null)
            {
                act(TaskDatabaseSession.CurrentSession);
            }
            else
            {
                TaskDatabaseSession.CurrentSession = NGinnDbSessionFactory.OpenSession();
                try
                {
                    act(TaskDatabaseSession.CurrentSession);
                }
                finally
                {
                    TaskDatabaseSession.CurrentSession.Dispose();
                    TaskDatabaseSession.CurrentSession = null;
                }
            }
        }
        
        /// <summary>
        /// Execute given action in modification mode.
        /// Task will be persisted afterwards.
        /// </summary>
        /// <param name="instanceId"></param>
        /// <param name="at"></param>
        private void ModifyTask(string instanceId, Action<TaskInstance> at)
        {
            log.Trace("Modifying task {0}", instanceId);
            DateTime st = DateTime.Now;
            bool kickable = false;
            NLog.MappedDiagnosticsContext.Set("ng_inst", instanceId);
            try
            {
                
                InTaskDbUpdateTransaction(delegate(ITaskDatabaseSession ses)
                {
                    using (IResourceLock lck = ProcessInstanceLockManager.AcquireWriteLock(instanceId))
                    {
                        TaskInstance ti = TaskInstanceRepository.GetTaskInstance(instanceId);
                        log.Debug("Task {0} retrieved", instanceId);
                        if (ti != null)
                        {
                            TaskContext tc = CreateTaskContext(ti.ProcessDefinitionId, ti.TaskId);
                            ti.Activate(tc);
                            at(ti);
                            ti.Passivate();
                            if (ti is CompositeTaskInstance || ti is ProcessInstance) kickable = ti.CanContinue;
                            TaskInstanceRepository.UpdateTaskInstance(ti);
                            log.Debug("Task {0} updated", instanceId);
                        }
                        else
                            at(null);
                    }

                    if (kickable)
                    {
                        InternalNGinnBus.NewMessage(new TaskKickableMessage(instanceId))
                            .SetNonPersistentLocal(ScheduleTasksWithMessageBus ? false : true).Publish();
                    }
                });
                log.Debug("Task {0} modification saved. Time: {1}", instanceId, DateTime.Now - st);
            }
            finally
            {
                NLog.MappedDiagnosticsContext.Remove("ng_inst");
            }
        }

        /// <summary>
        /// Execute given action in read mode (task is not persisted afterwards)
        /// </summary>
        /// <param name="instanceId"></param>
        /// <param name="at"></param>
        private void ReadTask(string instanceId, Action<TaskInstance> at)
        {
            TaskInstance ti = TaskInstanceRepository.GetTaskInstance(instanceId);
            if (ti != null)
            {
                TaskContext tc = CreateTaskContext(ti.ProcessDefinitionId, ti.TaskId);
                if (!string.IsNullOrEmpty(ti.ExtDocumentId))
                {
                    log.Warn("Document should be retrieved here: {0}", ti.ExtDocumentId);
                }
                ti.Activate(tc);
                at(ti);
                ti.Passivate();
            }
            else
                at(null);
        }

        #region INGEnvironment Members


        public string GetTaskState(string instanceId)
        {
            string ret = null;
            ReadTask(instanceId, delegate(TaskInstance ti)
            {
                System.IO.StringWriter sw = new System.IO.StringWriter();
                this.TaskInstanceSerializer.SerializeTaskInstance(ti, sw);
                ret = sw.ToString();
            });
            return ret;
        }

        public void ForceCompleteTask(string instanceId, Dictionary<string, object> updatedData, string completedBy)
        {
            try
            {
                ModifyTask(instanceId, delegate(TaskInstance ti)
                {
                    if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                    ti.Complete(completedBy, updatedData);
                });
            }
            catch (Exception ex)
            {
                log.Error("Error completing task {0}: {1}", instanceId, ex);
                throw;
            }
        }

        public void ForceFailTask(string instanceId, string errorInfo)
        {
            try
            {
                ModifyTask(instanceId, delegate(TaskInstance ti)
                {
                    if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                    ti.Fail(errorInfo);
                });
            }
            catch (Exception ex)
            {
                log.Error("Error failing task {0}: {1}", instanceId, ex);
                throw;
            }
        }

        /// <summary>
        /// Enable/disable checkpoint on a place.
        /// If checkpoint is enabled, tokens will stop at the place and will not be
        /// consumed by following tasks, effectively pausing the process execution.
        /// Disable checkpoint to continue the process execution.
        /// TODO: implement
        /// </summary>
        /// <param name="definitionId"></param>
        /// <param name="placeId"></param>
        /// <param name="enabled"></param>
        public void SetBarrier(string definitionId, string placeId, bool enabled)
        {
            _barrierRegistry.SetBarrier(definitionId, placeId, enabled);
        }

        /// <summary>
        /// Return checkpoint state for given place ID
        /// TODO: implement
        /// </summary>
        /// <param name="definitionId"></param>
        /// <param name="placeId"></param>
        /// <returns></returns>
        public bool GetBarrier(string definitionId, string placeId)
        {
            if (_barrierRegistry == null) return false;
            return _barrierRegistry.GetBarrier(definitionId, placeId);
        }

        public void SelectTask(string instanceId)
        {
            try
            {
                ModifyTask(instanceId, delegate(TaskInstance ti)
                {
                    if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                    ti.Select();
                });
            }
            catch (Exception ex)
            {
                log.Error("SelectTask failed to start task {0}: {1}", instanceId, ex);
                throw;
            }
        }

        #endregion

        #region INGEnvironment Members

        public string EnableMultiInstanceTask(string definitionId, string taskId, ICollection<Dictionary<string, object>> inputData, string parentTaskInstance, string parentProcessInstance)
        {
            string instanceId = NewInstanceId();
            try
            {
                NLog.MappedDiagnosticsContext.Set("ng_inst", instanceId);

                InTaskDbUpdateTransaction(delegate(ITaskDatabaseSession ses)
                {
                    using (IResourceLock lck = this.ProcessInstanceLockManager.AcquireWriteLock(instanceId))
                    {
                        EnableMultiInstanceTaskInternal(definitionId, taskId, inputData, parentTaskInstance, parentProcessInstance, instanceId);
                    }
                });
                return instanceId;
            }
            finally
            {
                NLog.MappedDiagnosticsContext.Remove("ng_inst");
            }
        }

        #endregion

        private TaskInstance EnableMultiInstanceTaskInternal(string definitionId, string taskId, ICollection<Dictionary<string, object>> inputData, string parentTaskInstance, string parentProcessInstance, string instanceId)
        {
            ProcessDefinition pd = PackageRepository.GetProcess(definitionId);
            Task tsk = pd.RequireTask(taskId);

            TaskInstance parent = null;
            if (parentTaskInstance != null)
            {
                parent = TaskInstanceRepository.GetTaskInstance(parentTaskInstance);
                if (parent == null)
                    throw new TaskInstanceNotFoundException(parentTaskInstance);
            }
            MultiTaskInstance mti = CreateMultiTask(pd, taskId);
            if (instanceId == null) instanceId = NewInstanceId();
            mti.InstanceId = instanceId;
            mti.ParentTaskInstanceId = parentTaskInstance;
            mti.ProcessInstanceId = parentProcessInstance;
            mti.Activate(CreateTaskContext(definitionId, taskId));
            mti.Enable(inputData);
            mti.Passivate();
            TaskInstanceRepository.InsertTaskInstance(mti);
            return mti;
        }

        #region INGEnvironment Members


        public Dictionary<string, object> GetTaskData(string instanceId)
        {
            Dictionary<string, object> dob = null;
            ReadTask(instanceId, delegate(TaskInstance ti)
            {
                if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                dob = new Dictionary<string, object>(ti.TaskData);
            });
            return dob;
        }

        public void UpdateTaskData(string instanceId, Dictionary<string, object> dataToUpdate)
        {
            try
            {
                ModifyTask(instanceId, delegate(TaskInstance ti)
                {
                    if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                    ti.UpdateTaskData(dataToUpdate);
                });
            }
            catch (Exception ex)
            {
                log.Error("Error updating data of task {0}: {1}", instanceId, ex);
                throw;
            }
        }

        /// <summary>
        /// TODO: this will fail if DontPersistCompletedTasks is true.
        /// </summary>
        /// <param name="instanceId"></param>
        public void RestartTask(string instanceId)
        {
            string parentId = null;
            ReadTask(instanceId, delegate(TaskInstance ti)
            {
                if (ti == null) throw new TaskInstanceNotFoundException(instanceId);
                parentId = ti.ParentTaskInstanceId;
            });
            if (parentId == null || parentId.Length == 0)
                throw new TaskRuntimeException("Restarting doesn't work for process instances or disconnected tasks").SetInstanceId(instanceId);
            ModifyTask(parentId, delegate(TaskInstance ti) {
                if (ti == null)
                    throw new TaskInstanceNotFoundException(parentId);
                if (ti is CompositeTaskInstance)
                {
                    CompositeTaskInstance cti = (CompositeTaskInstance)ti;
                    cti.RestartChildTransition(instanceId);
                }
                else if (ti is MultiTaskInstance)
                {
                    //TODO: implement
                    throw new NotImplementedException();
                }
                else throw new Exception();
            });
        }

        #endregion

        #region IMessageConsumer<ProcessStartedEvent> Members

        public void Handle(ProcessStartedEvent message)
        {
            log.Warn("Unhandled message: {0}", message);
        }

        #endregion

        #region IMessageConsumer<ProcessCompletedEvent> Members

        public void Handle(ProcessCompletedEvent message)
        {
            log.Warn("Unhandled message: {0}", message);
        }

        #endregion

        #region IMessageConsumer<StartProcessMessage> Members

        public void Handle(StartProcessMessage message)
        {
            try
            {
                string instId = this.StartProcess(message.DefinitionId, message.InputData, message.UserId, message.ProcessCorrelationId);
                ProcessStartedMessage psm = new ProcessStartedMessage();
                psm.InstanceId = instId;
                psm.Timestamp = DateTime.Now;
                InternalNGinnBus.Reply(psm);
            }
            catch (Exception ex)
            {
                //InternalNGinnBus.Reply(new FailureMessage<StartProcessMessage>(message.CorrelationId, message, ex.Message));
                throw;
                //TODO fix
            }
        }

        #endregion

        #region IMessageConsumer<EnableTaskMessage> Members

        public void Handle(EnableTaskMessage message)
        {
            try
            {
                TaskInstance ti = this.EnableTaskInternal(message.ProcessDefinition, message.TaskId, message.InputData, message.ParentTaskInstanceId, message.ProcessInstanceId, message.SharedId, message.NewTaskInstanceId);
                TaskEnabled tem = new TaskEnabled();
                tem.CorrelationId = message.CorrelationId;
                tem.ParentTaskInstanceId = message.ParentTaskInstanceId;
                tem.SourceTaskInstanceId = ti.InstanceId;
                tem.ProcessInstanceId = message.ProcessInstanceId;
                InternalNGinnBus.Reply(tem);
            }
            catch (NGinnException nex)
            {
                if (nex.Permanent)
                {
                    InternalNGinnBus.Reply(new TaskInstanceFailure(message.ParentTaskInstanceId, message.CorrelationId, nex.Message));
                    return;
                }
                throw;
            }
            catch (Exception ex)
            {
                //InternalNGinnBus.Reply(new FailureMessage<EnableTaskMessage>(message.CorrelationId, message, ex.Message));
                throw;

                //TODO FIX
            }
        }

        public void Handle(EnableMultiInstanceTaskMessage message)
        {
            try
            {
                TaskInstance ti = this.EnableMultiInstanceTaskInternal(message.ProcessDefinition, message.TaskId, message.MultiInputData, message.ParentTaskInstanceId, message.ProcessInstanceId, message.NewTaskInstanceId);
                TaskEnabled tem = new TaskEnabled();
                tem.SourceTaskInstanceId = ti.InstanceId;
                tem.ParentTaskInstanceId = message.ParentTaskInstanceId;
                tem.CorrelationId = message.CorrelationId;
                tem.ProcessInstanceId = message.ProcessInstanceId;
                InternalNGinnBus.Reply(tem);
            }
            catch (Exception ex)
            {
                //InternalNGinnBus.Reply(new FailureMessage<EnableMultiInstanceTaskMessage>(message.CorrelationId, message, ex.Message));
                throw;
            }
        }

        #endregion

        #region INGEnvironment Members


        public bool IsProcessTaskActive(string processInstance, string taskId)
        {
            bool b = false;
            ReadTask(processInstance, delegate(TaskInstance ti)
            {
                CompositeTaskInstance cti = ti as CompositeTaskInstance;
                if (cti == null) throw new Exception("Invalid process instance");
                TransitionInfo tri = cti.GetActiveInstanceOfTask(taskId);
                if (tri != null)
                    b = tri.IsTransitionActive;
            });
            return b;
        }

        public TaskInstanceInfo GetTaskInstanceInformation(string instanceId)
        {
            TaskInstanceInfo tii = null;
            this.ReadTask(instanceId, delegate(TaskInstance ti)
            {
                if (ti == null)
                    return;
                tii = new TaskInstanceInfo();
                tii.InstanceId = ti.InstanceId;
                tii.ParentTaskInstanceId = ti.ParentTaskInstanceId;
                tii.ProcessDefinitionId = ti.ProcessDefinitionId;
                tii.ProcessInstanceId = ti.ProcessInstanceId;
                tii.Status = ti.Status;
                tii.EnabledDate = ti.EnabledDate;
                tii.FinishedDate = ti.FinishedDate;
                tii.TaskId = ti.TaskId;
                tii.TaskData = ti.TaskData;
                if (ti is CompositeTaskInstance && (ti.Status == TaskStatus.Enabled || ti.Status == TaskStatus.Selected))
                {
                    CompositeTaskInstance cti = (CompositeTaskInstance)ti;
                    tii.Marking = new Dictionary<string, int>();
                    foreach (string k in cti.Marking.Keys)
                    {
                        int v = cti.Marking[k];
                        if (v > 0)
                            tii.Marking[k] = v;
                    }
                    tii.ActiveChildTasks = new Dictionary<string, string>();
                    foreach (TransitionInfo t in cti.ActiveTasks)
                    {
                        if (t.IsTransitionActive)
                        {
                            tii.ActiveChildTasks[t.InstanceId] = t.TaskId;
                        }
                    }
                }
            });
            return tii;
        }

        /// <summary>
        /// TODO: implement or delete
        /// </summary>
        /// <param name="processInstanceId"></param>
        /// <param name="taskId"></param>
        /// <returns></returns>
        public TaskInstanceInfo GetActiveTaskInstanceInformation(string processInstanceId, string taskId)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region INGEnvironment Members


        public string GetTaskStateString(string instanceId)
        {
            string ret = null;
            ReadTask(instanceId, delegate(TaskInstance ti)
            {
                if (ti != null)
                {
                    System.IO.StringWriter sw = new System.IO.StringWriter();
                    TaskInstanceSerializer.SerializeTaskInstance(ti, sw);
                    ret = sw.ToString();
                }
            });
            return ret;
        }

        public TaskInstanceInfo[] GetProcessesByExternalId(string extId)
        {
            throw new NotImplementedException();
        }

        public TaskInstanceInfo[] GetActiveProcessesByExternalId(string extId)
        {
            throw new NotImplementedException();
        }

        #endregion

        protected void DeliverToTaskInstance(ITaskInstanceMessage message) 
        {
            if (message.TargetTaskInstanceId == null)
                return;
            ModifyTask(message.TargetTaskInstanceId, delegate(TaskInstance ti) 
            {
                if (ti == null)
                {
                    log.Warn("Target task ({0}) not found for message {1}", message.TargetTaskInstanceId, message);
                    return;
                }
                ti.Handle(message);
            });
        }

        
        
        #region IMessageConsumer<CancelTaskMessage> Members

        public void Handle(CancelTaskMessage message)
        {
            ModifyTask(message.TargetTaskInstanceId, delegate(TaskInstance ti)
            {
                if (ti == null) throw new TaskInstanceNotFoundException(message.TargetTaskInstanceId);
                ti.Cancel();
            });
        }

        #endregion

        #region IMessageConsumer<FailTaskMessage> Members

        public void Handle(FailTaskMessage message)
        {
            try
            {
                ModifyTask(message.TargetTaskInstanceId, delegate(TaskInstance ti)
                {
                    if (ti == null) throw new TaskInstanceNotFoundException(message.TargetTaskInstanceId).SetPermanent(true);
                    ti.Fail(message.ErrorInformation);
                });
            }
            catch (NGinnException nex)
            {
                throw;
            }

        }

        #endregion



        #region INGEnvironment Members


        public void UpdateTaskData(string instanceId, IDictionary<string, object> dataToUpdate)
        {
            Dictionary<string, object> dob = new Dictionary<string, object>();
            foreach (string key in dataToUpdate.Keys)
                dob[key] = dataToUpdate[key];
            this.UpdateTaskData(instanceId, (Dictionary<string, object>)dob);
        }

        public void ForceCompleteTask(string instanceId, IDictionary<string, object> updatedData, string completedBy)
        {
            Dictionary<string, object> dob = new Dictionary<string, object>();
            foreach (string key in updatedData.Keys)
                dob[key] = updatedData[key];
            this.ForceCompleteTask(instanceId, dob, completedBy);
        }

        #endregion

        #region IMessageConsumer<WorkItemCompleted> Members

        /// <summary>
        /// this message is sent by ManualTaskInstance.
        /// </summary>
        /// <param name="message"></param>
        public void Handle(WorkItemCompleted message)
        {
            if (!string.IsNullOrEmpty(message.WorklistUrl))
            {
                var sc = new ServiceClient { BaseUrl = message.WorklistUrl };
                sc.CallService<NotifyWorkItemCompletedResponse>(new NotifyWorkItemCompleted { TaskInstanceId = message.TaskInstanceId, WorkitemId = message.WorkItemId });
            }
            else
            {
                IWorkListService ws = message.WorklistServiceName == null || message.WorklistServiceName.Length == 0 ? ServiceLocator.GetInstance<IWorkListService>() : ServiceLocator.GetInstance<IWorkListService>(message.WorklistServiceName);
                ws.WorkItemCompleted(message.WorkItemId, message.TaskInstanceId);
            }
        }

        #endregion

        #region IMessageConsumer<StartSubprocessMessage> Members

        /// <summary>
        /// TODO: if subprocess is started by a remote nginn, send back a response
        /// when it completes
        /// </summary>
        /// <param name="message"></param>
        public void Handle(StartSubprocessMessage message)
        {
            try
            {
                string instId = StartSubprocess(message.DefinitionId, message.InputData, message.ParentTaskInstanceId);
                InternalNGinnBus.Reply(new StartSubprocessMessageResponse { SubprocessInstanceId = instId, Success = true, TargetTaskInstanceId = message.ParentTaskInstanceId });
            }
            catch (NGinnException nex)
            {
                log.Error("Error starting subprocess: {0}", nex);
                InternalNGinnBus.Reply(new StartSubprocessMessageResponse { ErrorInfo = nex.Message, Success = false, TargetTaskInstanceId = message.ParentTaskInstanceId });
            }
            catch (Exception ex)
            {
                log.Error("Error starting subprocess: {0}", ex);
                throw;
            }
        }

        #endregion

        public void Handle(ITaskInstanceMessage message)
        {
            log.Debug("Delivering message {0} to task {1}", message.GetType().FullName, message.TargetTaskInstanceId);
            DeliverToTaskInstance(message);
        }
    }
}
